﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;

using Spencen.Mobile.Markup.Extensions;
using Spencen.Mobile.UI.Behaviors;
using Spencen.Mobile.UI.Layout;
using Spencen.Mobile.UI.Transforms;
using Spencen.Mobile.UI.Primitives;

using Microsoft.Drawing;

namespace Spencen.Mobile.UI
{
    public abstract class DrawingElement : IResourceDictionary
    {
        private static readonly Color TransparencyMaskColor = Color.FromArgb( 254, 254, 254 );

        public static readonly Pen DefaultStroke = new Pen( Color.Black );
        public static readonly Brush DefaultBackground = new SolidBrush( Color.White );
        public static readonly Pen BoundsPen = new Pen( Color.Gray ) { DashStyle = System.Drawing.Drawing2D.DashStyle.Dash };

        private Size _size;
        private DesiredSize _desiredSize;
        private Bitmap _retainedBitmap;
        private bool _retainedInvalidated;
        private bool _drawBounds = false; // Use this flag to debug bounding rectangles used for clipping

        protected DrawingElement()
        {
            Transforms = new List<Transform>();
            Behaviors = new BehaviorCollection( this );
            Resources = new Dictionary<string, object>();
            Stroke = DefaultStroke;
            Background = DefaultBackground;
            IsHitTestVisible = true;
        }

        public DrawingElement Parent { get; protected internal set; }
        public float Angle { get; set; }
        public Point Position { get; set; }
        public Brush Background { get; set; }
        public Pen Stroke { get; set; }
        public bool IsHitTestVisible { get; set; }
        public bool IsRetained { get; set; }
        public bool CanFocus { get; set; }
        internal bool HasFocus { get; set; }

        public IList<Transform> Transforms { get; private set; }
        public IList<IBehavior> Behaviors { get; private set; }
        public IDictionary<string, object> Resources { get; private set; }
        public IDrawingHost Host { get; internal set; }
        public HorizontalElementAlignment HorizontalAlignment { get; set; }
        public VerticalElementAlignment VerticalAlignment { get; set; }

        public virtual bool SupportsRotation { get { return true; } }

        public Size Size
        {
            get
            {
                return GetActualSize();
            }
            set
            {
                if ( _size != value )
                {
                    _size = value;
                    OnResize();
                }
            }
        }

        private Size GetActualSize()
        {
            if ( _size.Width != 0 && _size.Height != 0 ) return _size;

            int actualWidth = _size.Width;
            int actualHeight = _size.Height;
            
            if ( _size.Width == 0 )
            {
                if ( DesiredSize.IsXUnbound )
                {
                    if ( Parent != null )
                        actualWidth = Parent.Size.Width;
                    else
                        actualWidth = Host.Size.Width;
                }
                else if ( Host != null )
                    actualWidth = DesiredSize.Width * Host.Dpi.X / Host.TargetDpi.X;
            }
            if ( _size.Height == 0 )
            {
                if ( DesiredSize.IsYUnbound )
                {
                    if ( Parent != null )
                        actualHeight = Parent.Size.Height;
                    else
                        actualHeight = Host.Size.Height;
                }
                else if ( Host != null )
                    actualHeight = DesiredSize.Height * Host.Dpi.Y / Host.TargetDpi.Y;
            }

            return new Size( actualWidth, actualHeight );
        }

        public DesiredSize DesiredSize
        {
            get { return _desiredSize; }
            set 
            {
                if ( _desiredSize != value )
                {
                    _desiredSize = value;
                    Invalidate();
                }
            }
        }

        public Point Center 
        { 
            get { return new Point( Position.X + Size.Width / 2, Position.Y + Size.Height / 2 ); }
            set
            {
                Position = new Point( value.X - Size.Width / 2, value.Y - Size.Height / 2 );
            }
        }

        public Point RelativeCenter
        {
            get { return new Point( Size.Width / 2, Size.Height / 2 ); }
        }

        public virtual bool HitTest( Point hitPoint, GraphicsContext context )
        {
            return false; // Non-interactive by default
        }

        public virtual Rectangle TransformedBounds( GraphicsContext context )
        {
            var boundingPoints = new Point[]
            {
                new Point( 0, 0 ),
                new Point( Size.Width, 0 ),
                new Point( Size.Width, Size.Height ),
                new Point( 0, Size.Height )
            };
            var transformedPoints = context.TransformedPoints( boundingPoints );
            var minX = transformedPoints.Min( p => p.X );
            var minY = transformedPoints.Min( p => p.Y );
            var maxX = transformedPoints.Max( p => p.X );
            var maxY = transformedPoints.Max( p => p.Y );

            var transformedBounds = new Rectangle( minX, minY, maxX - minX, maxY - minY );

            return transformedBounds;
        }

        protected virtual void OnResize()
        {
            _retainedInvalidated = true;
        }

        public void Render( GraphicsContext context )
        {
            if ( DesiredSize == DesiredSize.Unbound )
                if ( Parent != null )
                    Size = Parent.Size;
                else
                    Size = Host.Size;

            if ( IsRetained && !_retainedInvalidated && _retainedBitmap != null )
            {
                if ( Background == null )
                    //context.Graphics.DrawImage( _retainedBitmap, Position.X, Position.Y );
                    //context.Graphics.DrawAlpha( _retainedBitmap, 200, Position.X, Position.Y );
                    //context.Graphics.DrawImageAlphaChannel( _retainedBitmap, new Rectangle( Position.X, Position.Y, Size.Width, Size.Height ) );
                    context.Graphics.DrawImageTransparent( _retainedBitmap, new Rectangle( Position.X, Position.Y, Size.Width, Size.Height ), TransparencyMaskColor );
                else
                    context.Graphics.DrawImage( _retainedBitmap, Position.X, Position.Y );
            }
            else
            {
                var bounds = TransformedBounds( context );
                if ( bounds == Rectangle.Empty || bounds.IntersectsWith( context.ClipBounds ) )
                {
                    //System.Diagnostics.Debug.WriteLine( string.Format( "Render {4}: ( X={0}, Y={1}, Width={2}, Height={2})", bounds.X, bounds.Y, bounds.Width, bounds.Height, this.GetType().Name ) );
                    OnRender( context );
                    if ( _drawBounds )
                        context.Graphics.DrawRectangle( BoundsPen, TransformedBounds( context ) );
                }
                else
                {
                    //System.Diagnostics.Debug.WriteLine( string.Format( "Optimize Render by excluding  {4}: ( X={0}, Y={1}, Width={2}, Height={2})", bounds.X, bounds.Y, bounds.Width, bounds.Height, this.GetType().Name ) );
                }
            }

            if ( IsRetained && Angle == 0 && _retainedInvalidated )
            {
                _retainedBitmap = new Bitmap(Size.Width, Size.Height );
                using ( var retainedGraphics = Graphics.FromImage( _retainedBitmap ) )
                {
                    if ( Background == null )
                        retainedGraphics.Clear( TransparencyMaskColor ); // Use Magenta as the transparency mask
                        //retainedGraphics.Clear( Color.Transparent ); // Transparency doesn't work :-(

                    var tempContext = new GraphicsContext( retainedGraphics );
                    OnRender( tempContext );
                }
                _retainedInvalidated = false;
            }
        }

        protected abstract void OnRender( GraphicsContext context );

        public void Invalidate()
        {
            if ( Host != null )
                Host.Invalidate( this );

            //var container = Parent as DrawingContainer;
            //if ( container != null )
            //    container.LayoutChild( this );
        }

        #region Interactive Stuff - derived class? behaviour?

        public event EventHandler<MouseEventArgs> MouseDown;
        public event EventHandler<MouseEventArgs> MouseUp;
        public event EventHandler<MouseEventArgs> MouseMove;

        protected internal virtual void OnMouseDown( MouseEventArgs e )
        {
            var handler = MouseDown;
            if ( handler != null )
                handler( this, e );
        }

        protected internal virtual void OnMouseUp( MouseEventArgs e )
        {
            var handler = MouseUp;
            if ( handler != null )
                handler( this, e );
        }

        protected internal virtual void OnMouseMove( MouseEventArgs e )
        {
            var handler = MouseMove;
            if ( handler != null )
                handler( this, e );
        }

        #endregion

        private class BehaviorCollection : Collection<IBehavior>
        {
            private DrawingElement _parent;

            public BehaviorCollection( DrawingElement parent )
            {
                _parent = parent;
            }

            protected override void InsertItem( int index, IBehavior item )
            {
                base.InsertItem( index, item );

                item.OnAttached( _parent );
            }

            protected override void RemoveItem( int index )
            {
                var item = Items[ index ];
                item.OnDettached( _parent );

                base.RemoveItem( index );

            }
        }

        #region IResourceDictionary Members

        IResourceDictionary IResourceDictionary.Parent
        {
            get { return this.Parent; }
        }

        #endregion
    }
}
